home *** CD-ROM | disk | FTP | other *** search
/ BCI NET 2 / BCI NET 2.iso / archives / utilities / text / less-278.lha / less-278 / src.lha / source / lesskey.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-02-01  |  14.0 KB  |  690 lines

  1. /*
  2.  * Copyright (c) 1984,1985,1989,1994,1995  Mark Nudelman
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice, this list of conditions and the following disclaimer.
  10.  * 2. Redistributions in binary form must reproduce the above copyright
  11.  *    notice in the documentation and/or other materials provided with 
  12.  *    the distribution.
  13.  *
  14.  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
  15.  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  16.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  17.  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
  18.  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
  19.  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
  20.  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
  21.  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
  22.  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
  23.  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 
  24.  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25.  */
  26.  
  27.  
  28. /*
  29.  *    lesskey [-o output] [input]
  30.  *
  31.  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
  32.  *
  33.  *    Make a .less file.
  34.  *    If no input file is specified, standard input is used.
  35.  *    If no output file is specified, $HOME/.less is used.
  36.  *
  37.  *    The .less file is used to specify (to "less") user-defined
  38.  *    key bindings.  Basically any sequence of 1 to MAX_CMDLEN
  39.  *    keystrokes may be bound to an existing less function.
  40.  *
  41.  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
  42.  *
  43.  *    The input file is an ascii file consisting of a 
  44.  *    sequence of lines of the form:
  45.  *        string <whitespace> action [chars] <newline>
  46.  *
  47.  *    "string" is a sequence of command characters which form
  48.  *        the new user-defined command.  The command
  49.  *        characters may be:
  50.  *        1. The actual character itself.
  51.  *        2. A character preceded by ^ to specify a
  52.  *           control character (e.g. ^X means control-X).
  53.  *        3. A backslash followed by one to three octal digits
  54.  *           to specify a character by its octal value.
  55.  *        4. A backslash followed by b, e, n, r or t
  56.  *           to specify \b, ESC, \n, \r or \t, respectively.
  57.  *        5. Any character (other than those mentioned above) preceded 
  58.  *           by a \ to specify the character itself (characters which
  59.  *           must be preceded by \ include ^, \, and whitespace.
  60.  *    "action" is the name of a "less" action, from the table below.
  61.  *    "chars" is an optional sequence of characters which is treated
  62.  *        as keyboard input after the command is executed.
  63.  *
  64.  *    Blank lines and lines which start with # are ignored, 
  65.  *    except for the special control lines:
  66.  *        #line-edit    Signals the beginning of the line-editing
  67.  *                keys section.
  68.  *        #stop        Stops command parsing in less;
  69.  *                causes all default keys to be disabled.
  70.  *
  71.  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
  72.  *
  73.  *    The output file is a non-ascii file, consisting of a header,
  74.  *    one or more sections, and a trailer.
  75.  *    Each section begins with a section header, a section length word
  76.  *    and the section data.  Normally there are three sections:
  77.  *        CMD_SECTION    Definition of command keys.
  78.  *        EDIT_SECTION    Definition of editing keys.
  79.  *        END_SECTION    A special section header, with no 
  80.  *                length word or section data.
  81.  *
  82.  *    Section data consists of zero or more byte sequences of the form:
  83.  *        string <0> <action>
  84.  *    or
  85.  *        string <0> <action|A_EXTRA> chars <0>
  86.  *
  87.  *    "string" is the command string.
  88.  *    "<0>" is one null byte.
  89.  *    "<action>" is one byte containing the action code (the A_xxx value).
  90.  *    If action is ORed with A_EXTRA, the action byte is followed
  91.  *        by the null-terminated "chars" string.
  92.  *
  93.  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
  94.  */
  95.  
  96. #include "less.h"
  97. #include "lesskey.h"
  98. #include "cmd.h"
  99.  
  100. struct cmdname
  101. {
  102.     char *cn_name;
  103.     int cn_action;
  104. };
  105.  
  106. struct cmdname cmdnames[] = 
  107. {
  108.     "back-bracket",        A_B_BRACKET,
  109.     "back-line",        A_B_LINE,
  110.     "back-line-force",    A_BF_LINE,
  111.     "back-screen",        A_B_SCREEN,
  112.     "back-scroll",        A_B_SCROLL,
  113.     "back-search",        A_B_SEARCH,
  114.     "back-window",        A_B_WINDOW,
  115.     "debug",        A_DEBUG,
  116.     "display-flag",        A_DISP_OPTION,
  117.     "display-option",    A_DISP_OPTION,
  118.     "end",            A_GOEND,
  119.     "examine",        A_EXAMINE,
  120.     "first-cmd",        A_FIRSTCMD,
  121.     "firstcmd",        A_FIRSTCMD,
  122.     "flush-repaint",    A_FREPAINT,
  123.     "forw-bracket",        A_F_BRACKET,
  124.     "forw-forever",        A_F_FOREVER,
  125.     "forw-line",        A_F_LINE,
  126.     "forw-line-force",    A_FF_LINE,
  127.     "forw-screen",        A_F_SCREEN,
  128.     "forw-scroll",        A_F_SCROLL,
  129.     "forw-search",        A_F_SEARCH,
  130.     "forw-window",        A_F_WINDOW,
  131.     "goto-end",        A_GOEND,
  132.     "goto-line",        A_GOLINE,
  133.     "goto-mark",        A_GOMARK,
  134.     "help",            A_HELP,
  135.     "index-file",        A_INDEX_FILE,
  136.     "invalid",        A_UINVALID,
  137.     "next-file",        A_NEXT_FILE,
  138.     "noaction",        A_NOACTION,
  139.     "percent",        A_PERCENT,
  140.     "pipe",            A_PIPE,
  141.     "prev-file",        A_PREV_FILE,
  142.     "quit",            A_QUIT,
  143.     "repaint",        A_REPAINT,
  144.     "repaint-flush",    A_FREPAINT,
  145.     "repeat-search",    A_AGAIN_SEARCH,
  146.     "repeat-search-all",    A_T_AGAIN_SEARCH,
  147.     "reverse-search",    A_REVERSE_SEARCH,
  148.     "reverse-search-all",    A_T_REVERSE_SEARCH,
  149.     "set-mark",        A_SETMARK,
  150.     "shell",        A_SHELL,
  151.     "status",        A_STAT,
  152.     "toggle-flag",        A_OPT_TOGGLE,
  153.     "toggle-option",    A_OPT_TOGGLE,
  154.     "undo-hilite",        A_UNDO_SEARCH,
  155.     "version",        A_VERSION,
  156.     "visual",        A_VISUAL,
  157.     NULL,            0
  158. };
  159.  
  160. struct cmdname editnames[] = 
  161. {
  162.     "back-complete",    EC_B_COMPLETE,
  163.     "backspace",        EC_BACKSPACE,
  164.     "delete",        EC_DELETE,
  165.     "down",            EC_DOWN,
  166.     "end",            EC_END,
  167.     "expand",        EC_EXPAND,
  168.     "forw-complete",    EC_F_COMPLETE,
  169.     "home",            EC_HOME,
  170.     "insert",        EC_INSERT,
  171.     "invalid",        EC_UINVALID,
  172.     "kill-line",        EC_LINEKILL,
  173.     "left",            EC_LEFT,
  174.     "literal",        EC_LITERAL,
  175.     "right",        EC_RIGHT,
  176.     "up",            EC_UP,
  177.     "word-backspace",    EC_W_BACKSPACE,
  178.     "word-delete",        EC_W_DELETE,
  179.     "word-left",        EC_W_RIGHT,
  180.     "word-right",        EC_W_LEFT,
  181.     NULL,            0
  182. };
  183.  
  184. struct table
  185. {
  186.     struct cmdname *names;
  187.     char *pbuffer;
  188.     char buffer[MAX_USERCMD];
  189. };
  190.  
  191. struct table cmdtable;
  192. struct table edittable;
  193. struct table *currtable = &cmdtable;
  194.  
  195. char fileheader[] = {
  196.     C0_LESSKEY_MAGIC, 
  197.     C1_LESSKEY_MAGIC, 
  198.     C2_LESSKEY_MAGIC, 
  199.     C3_LESSKEY_MAGIC
  200. };
  201. char filetrailer[] = {
  202.     C0_END_LESSKEY_MAGIC, 
  203.     C1_END_LESSKEY_MAGIC, 
  204.     C2_END_LESSKEY_MAGIC
  205. };
  206. char cmdsection[] =    { CMD_SECTION };
  207. char editsection[] =    { EDIT_SECTION };
  208. char endsection[] =    { END_SECTION };
  209.  
  210. char *infile = NULL;
  211. char *outfile = NULL ;
  212.  
  213. int linenum;
  214. int errors;
  215.  
  216. extern char version[];
  217.  
  218.     char *
  219. mkpathname(dirname, filename)
  220.     char *dirname;
  221.     char *filename;
  222. {
  223.     char *pathname;
  224.  
  225.     pathname = calloc(strlen(dirname) + strlen(filename) + 2, sizeof(char));
  226.     strcpy(pathname, dirname);
  227. #if MSOFTC || OS2
  228.     strcat(pathname, "\\");
  229. #else
  230.     strcat(pathname, "/");
  231. #endif
  232.     strcat(pathname, filename);
  233.     return (pathname);
  234. }
  235.  
  236. /*
  237.  * Figure out the name of a default file (in the user's HOME directory).
  238.  */
  239.     char *
  240. homefile(filename)
  241.     char *filename;
  242. {
  243.     char *p;
  244.     char *pathname;
  245.  
  246. #if OS2
  247.     if ((p = getenv("INIT")) != NULL && *p != '\0')
  248.         pathname = mkpathname(p, filename);
  249.     else
  250. #endif
  251.     if ((p = getenv("HOME")) != NULL && *p != '\0')
  252.     {
  253.         pathname = mkpathname(p, filename);
  254.     } else
  255.     {
  256.         fprintf(stderr, "cannot find $HOME - using current directory\n");
  257.         pathname = mkpathname(".", filename);
  258.     }
  259.     return (pathname);
  260. }
  261.  
  262. /*
  263.  * Parse command line arguments.
  264.  */
  265.     void
  266. parse_args(argc, argv)
  267.     int argc;
  268.     char **argv;
  269. {
  270.     outfile = NULL;
  271.     while (--argc > 0 && **(++argv) == '-' && argv[0][1] != '\0')
  272.     {
  273.         switch (argv[0][1])
  274.         {
  275.         case 'o':
  276.             outfile = &argv[0][2];
  277.             if (*outfile == '\0')
  278.             {
  279.                 if (--argc <= 0)
  280.                     usage();
  281.                 outfile = *(++argv);
  282.             }
  283.             break;
  284.         case 'V':
  285.             printf("lesskey  version %s\n", version);
  286.             exit(0);
  287.         default:
  288.             usage();
  289.         }
  290.     }
  291.     if (argc > 1)
  292.         usage();
  293.     /*
  294.      * Open the input file, or use DEF_LESSKEYINFILE if none specified.
  295.      */
  296.     if (argc > 0)
  297.         infile = *argv;
  298.     else
  299.         infile = homefile(DEF_LESSKEYINFILE);
  300. }
  301.  
  302. /*
  303.  * Initialize data structures.
  304.  */
  305.     void
  306. init_tables()
  307. {
  308.     cmdtable.names = cmdnames;
  309.     cmdtable.pbuffer = cmdtable.buffer;
  310.  
  311.     edittable.names = editnames;
  312.     edittable.pbuffer = edittable.buffer;
  313. }
  314.  
  315. /*
  316.  * Parse one character of a string.
  317.  */
  318.     int
  319. tchar(pp)
  320.     char **pp;
  321. {
  322.     register char *p;
  323.     register char ch;
  324.     register int i;
  325.  
  326.     p = *pp;
  327.     switch (*p)
  328.     {
  329.     case '\\':
  330.         ++p;
  331.         switch (*p)
  332.         {
  333.         case '0': case '1': case '2': case '3':
  334.         case '4': case '5': case '6': case '7':
  335.             /*
  336.              * Parse an octal number.
  337.              */
  338.             ch = 0;
  339.             i = 0;
  340.             do
  341.                 ch = 8*ch + (*p - '0');
  342.             while (*++p >= '0' && *p <= '7' && ++i < 3);
  343.             *pp = p;
  344.             return (ch);
  345.         case 'b':
  346.             *pp = p+1;
  347.             return ('\r');
  348.         case 'e':
  349.             *pp = p+1;
  350.             return (ESC);
  351.         case 'n':
  352.             *pp = p+1;
  353.             return ('\n');
  354.         case 'r':
  355.             *pp = p+1;
  356.             return ('\r');
  357.         case 't':
  358.             *pp = p+1;
  359.             return ('\t');
  360.         default:
  361.             /*
  362.              * Backslash followed by any other char 
  363.              * just means that char.
  364.              */
  365.             *pp = p+1;
  366.             return (*p);
  367.         }
  368.     case '^':
  369.         /*
  370.          * Carat means CONTROL.
  371.          */
  372.         *pp = p+2;
  373.         return (CONTROL(p[1]));
  374.     }
  375.     *pp = p+1;
  376.     return (*p);
  377. }
  378.  
  379. /*
  380.  * Skip leading spaces in a string.
  381.  */
  382.     public char *
  383. skipsp(s)
  384.     register char *s;
  385. {
  386.     while (*s == ' ' || *s == '\t')    
  387.         s++;
  388.     return (s);
  389. }
  390.  
  391. /*
  392.  * Skip non-space characters in a string.
  393.  */
  394.     public char *
  395. skipnsp(s)
  396.     register char *s;
  397. {
  398.     while (*s != '\0' && *s != ' ' && *s != '\t')
  399.         s++;
  400.     return (s);
  401. }
  402.  
  403. /*
  404.  * Clean up an input line:
  405.  * strip off the trailing newline & any trailing # comment.
  406.  */
  407.     char *
  408. clean_line(s)
  409.     char *s;
  410. {
  411.     register int i;
  412.  
  413.     s = skipsp(s);
  414.     for (i = 0;  s[i] != '\n' && s[i] != '\0';  i++)
  415.         if (s[i] == '#' && (i == 0 || s[i-1] != '\\'))
  416.             break;
  417.     s[i] = '\0';
  418.     return (s);
  419. }
  420.  
  421. /*
  422.  * Add a byte to the output command table.
  423.  */
  424.     void
  425. add_cmd_char(c)
  426.     int c;
  427. {
  428.     if (currtable->pbuffer >= currtable->buffer + MAX_USERCMD)
  429.     {
  430.         error("too many commands");
  431.         exit(1);
  432.     }
  433.     *(currtable->pbuffer)++ = c;
  434. }
  435.  
  436. /*
  437.  * See if we have a special "control" line.
  438.  */
  439.     int
  440. control_line(s)
  441.     char *s;
  442. {
  443. #define    PREFIX(str,pat)    (strncmp(str,pat,strlen(pat)-1) == 0)
  444.  
  445.     if (PREFIX(s, "#line-edit"))
  446.     {
  447.         currtable = &edittable;
  448.         return (1);
  449.     }
  450.     if (PREFIX(s, "#command"))
  451.     {
  452.         currtable = &cmdtable;
  453.         return (1);
  454.     }
  455.     if (PREFIX(s, "#stop"))
  456.     {
  457.         add_cmd_char('\0');
  458.         add_cmd_char(A_END_LIST);
  459.         return (1);
  460.     }
  461.     return (0);
  462. }
  463.  
  464. /*
  465.  * Output some bytes.
  466.  */
  467.     void
  468. fputbytes(fd, buf, len)
  469.     FILE *fd;
  470.     char *buf;
  471.     int len;
  472. {
  473.     while (len-- > 0)
  474.     {
  475.         fwrite(buf, sizeof(char), 1, fd);
  476.         buf++;
  477.     }
  478. }
  479.  
  480. /*
  481.  * Output an integer, in special KRADIX form.
  482.  */
  483.     void
  484. fputint(fd, val)
  485.     FILE *fd;
  486.     unsigned int val;
  487. {
  488.     char c;
  489.  
  490.     if (val >= KRADIX*KRADIX)
  491.     {
  492.         fprintf(stderr, "error: integer too big (%d > %d)\n", 
  493.             val, KRADIX*KRADIX);
  494.         exit(1);
  495.     }
  496.     c = val % KRADIX;
  497.     fwrite(&c, sizeof(char), 1, fd);
  498.     c = val / KRADIX;
  499.     fwrite(&c, sizeof(char), 1, fd);
  500. }
  501.  
  502. /*
  503.  * Find an action, given the name of the action.
  504.  */
  505.     int
  506. findaction(actname)
  507.     char *actname;
  508. {
  509.     int i;
  510.  
  511.     for (i = 0;  currtable->names[i].cn_name != NULL;  i++)
  512.         if (strcmp(currtable->names[i].cn_name, actname) == 0)
  513.             return (currtable->names[i].cn_action);
  514.     error("unknown action");
  515.     return (A_INVALID);
  516. }
  517.  
  518. usage()
  519. {
  520.     fprintf(stderr, "usage: lesskey [-o output] [input]\n");
  521.     exit(1);
  522. }
  523.  
  524.     void
  525. error(s)
  526.     char *s;
  527. {
  528.     fprintf(stderr, "line %d: %s\n", linenum, s);
  529.     errors++;
  530. }
  531.  
  532.  
  533. /*
  534.  * Parse a line from the lesskey file.
  535.  */
  536.     void
  537. parse_line(line)
  538.     char *line;
  539. {
  540.     char *p;
  541.     int cmdlen;
  542.     char *actname;
  543.     int action;
  544.     int c;
  545.  
  546.     /*
  547.      * See if it is a control line.
  548.      */
  549.     if (control_line(line))
  550.         return;
  551.     /*
  552.      * Skip leading white space.
  553.      * Replace the final newline with a null byte.
  554.      * Ignore blank lines and comments.
  555.      */
  556.     p = clean_line(line);
  557.     if (*p == '\0')
  558.         return;
  559.  
  560.     /*
  561.      * Parse the command string and store it in the current table.
  562.      */
  563.     cmdlen = 0;
  564.     do
  565.     {
  566.         c = tchar(&p);
  567.         if (++cmdlen > MAX_CMDLEN)
  568.             error("command too long");
  569.         else
  570.             add_cmd_char(c);
  571.     } while (*p != ' ' && *p != '\t' && *p != '\0');
  572.     /*
  573.      * Terminate the command string with a null byte.
  574.      */
  575.     add_cmd_char('\0');
  576.  
  577.     /*
  578.      * Skip white space between the command string
  579.      * and the action name.
  580.      * Terminate the action name with a null byte.
  581.      */
  582.     p = skipsp(p);
  583.     if (*p == '\0')
  584.     {
  585.         error("missing action");
  586.         return;
  587.     }
  588.     actname = p;
  589.     p = skipnsp(p);
  590.     c = *p;
  591.     *p = '\0';
  592.  
  593.     /*
  594.      * Parse the action name and store it in the current table.
  595.      */
  596.     action = findaction(actname);
  597.  
  598.     /*
  599.      * See if an extra string follows the action name.
  600.      */
  601.     *p = c;
  602.     p = skipsp(p);
  603.     if (*p == '\0')
  604.     {
  605.         add_cmd_char(action);
  606.     } else
  607.     {
  608.         /*
  609.          * OR the special value A_EXTRA into the action byte.
  610.          * Put the extra string after the action byte.
  611.          */
  612.         add_cmd_char(action | A_EXTRA);
  613.         while (*p != '\0')
  614.             add_cmd_char(tchar(&p));
  615.         add_cmd_char('\0');
  616.     }
  617. }
  618.  
  619. main(argc, argv)
  620.     int argc;
  621.     char *argv[];
  622. {
  623.     FILE *desc;
  624.     FILE *out;
  625.     char line[200];
  626.  
  627.     /*
  628.      * Process command line arguments.
  629.      */
  630.     parse_args(argc, argv);
  631.     init_tables();
  632.  
  633.     /*
  634.      * Open the input file.
  635.      */
  636.     if (strcmp(infile, "-") == 0)
  637.         desc = stdin;
  638.     else if ((desc = fopen(infile, "r")) == NULL)
  639.     {
  640.         perror(infile);
  641.         exit(1);
  642.     }
  643.  
  644.     /*
  645.      * Read and parse the input file, one line at a time.
  646.      */
  647.     errors = 0;
  648.     linenum = 0;
  649.     while (fgets(line, sizeof(line), desc) != NULL)
  650.     {
  651.         ++linenum;
  652.         parse_line(line);
  653.     }
  654.  
  655.     /*
  656.      * Write the output file.
  657.      * If no output file was specified, use "$HOME/.less"
  658.      */
  659.     if (errors > 0)
  660.     {
  661.         fprintf(stderr, "%d errors; no output produced\n", errors);
  662.         exit(1);
  663.     }
  664.  
  665.     if (outfile == NULL)
  666.         outfile = homefile(LESSKEYFILE);
  667.     if ((out = fopen(outfile, "w")) == NULL)
  668.     {
  669.         perror(outfile);
  670.         exit(1);
  671.     }
  672.  
  673.     /* File header */
  674.     fputbytes(out, fileheader, sizeof(fileheader));
  675.  
  676.     /* Command key section */
  677.     fputbytes(out, cmdsection, sizeof(cmdsection));
  678.     fputint(out, cmdtable.pbuffer - cmdtable.buffer);
  679.     fputbytes(out, (char *)cmdtable.buffer, cmdtable.pbuffer-cmdtable.buffer);
  680.     /* Edit key section */
  681.     fputbytes(out, editsection, sizeof(editsection));
  682.     fputint(out, edittable.pbuffer - edittable.buffer);
  683.     fputbytes(out, (char *)edittable.buffer, edittable.pbuffer-edittable.buffer);
  684.  
  685.     /* File trailer */
  686.     fputbytes(out, endsection, sizeof(endsection));
  687.     fputbytes(out, filetrailer, sizeof(filetrailer));
  688.     exit(0);
  689. }
  690.